/**********************************************
*  Classes:   Loader3ds
*  Desc:      Loads .3ds files and creates
*		      MeshNodes
*  Notes:     Contains code by Damiano Vitulli
*  Author:    Vaios Kalpias-Ilias (C) 2008-2009
***********************************************/

#pragma warning( disable : 4996 )
#include "Loader3ds.h"
#include "GeometryNode.h"
#include "CdnRenderer.h"
#include "CdnResourceLoader.h"
#include "BoundingBox.h"
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <io.h>

using namespace mth;
//----------------------------------------------
/**
* Ctor
*/
Loader3ds::Loader3ds()
{
}
//----------------------------------------------
/**
* Dtor
*/
Loader3ds::~Loader3ds()
{
}
//----------------------------------------------
/**
* Loads a 3ds file and creates
* a meshnode to display it
*/
GeometryNode* Loader3ds::Load(const char *filename, CdnRenderer* renderer)
{
	// vertices, normals and texture coordinates
	// to be read by the file
	Vector3* vertices_unordered = 0;
	Index* indices = 0;
	Vector2* texCoords_unordered = 0;
	
	// object name
	char name[255];

	// the resulting node;
	GeometryNode* mesh = new GeometryNode();
	if (!mesh) return 0;

	//Index variable
	int i; 
	
	//File pointer
	FILE *l_file; 
	
	//Chunk identifier
	unsigned short l_chunk_id; 
	//Chunk lenght
	unsigned int l_chunk_lenght; 
	//Char variable
	unsigned char l_char; 
	//Number of elements in each chunk
	unsigned short l_qty; 
	// number of polygons 
	int numOfPolygons = 0;
	//Flag that stores some face information
	unsigned short l_face_flags; 

	//Open the file
	if ((l_file=fopen (filename, "rb"))== 0) return 0; 

	//Loop to scan the whole file 
	while (ftell (l_file) < filelength (fileno (l_file))) 
	{
		// Insert this command for 
		// debug (to wait for keypress 
		// for each chuck reading)
		//getche(); 

		//Read the chunk header
		fread (&l_chunk_id, 2, 1, l_file); 
		//printf("ChunkID: %x\n",l_chunk_id); 
		//Read the lenght of the chunk
		fread (&l_chunk_lenght, 4, 1, l_file); 
		//printf("ChunkLenght: %x\n",l_chunk_lenght);

		switch (l_chunk_id)
        {
			//----------------- MAIN3DS -----------------
			// Description: Main chunk, contains all the other chunks
			// Chunk ID: 4d4d 
			// Chunk Lenght: 0 + sub chunks
			//-------------------------------------------
			case 0x4d4d: 
			break;    

			//----------------- EDIT3DS -----------------
			// Description: 3D Editor chunk, objects layout info 
			// Chunk ID: 3d3d (hex)
			// Chunk Lenght: 0 + sub chunks
			//-------------------------------------------
			case 0x3d3d:
			break;
			
			//--------------- EDIT_OBJECT ---------------
			// Description: Object block, info for each object
			// Chunk ID: 4000 (hex)
			// Chunk Lenght: len(object name) + sub chunks
			//-------------------------------------------
			case 0x4000: 
				i=0;				
				do
				{
					fread (&l_char, 1, 1, l_file);
                    name[i] = l_char;
					i++;
                }while(l_char != '\0' && i<255);

				mesh->SetName(name);
				
			break;

			//--------------- OBJ_TRIMESH ---------------
			// Description: Triangular mesh, contains chunks for 3d mesh info
			// Chunk ID: 4100 (hex)
			// Chunk Lenght: 0 + sub chunks
			//-------------------------------------------
			case 0x4100:
			break;
			
			//--------------- TRI_VERTEXL ---------------
			// Description: Vertices list
			// Chunk ID: 4110 (hex)
			// Chunk Lenght: 1 x unsigned short (number of vertices) 
			//             + 3 x float (vertex coordinates) x (number of vertices)
			//             + sub chunks
			//-------------------------------------------
			case 0x4110: 
				fread (&l_qty, sizeof (unsigned short), 1, l_file);
                // allocate memory for vertices
				
				vertices_unordered = new Vector3[l_qty];
				if (!vertices_unordered) return 0;
                //printf("Number of vertices: %d\n",l_qty);
                for (i=0; i<l_qty; i++)
                {
					fread (&vertices_unordered[i].x, sizeof(float), 1, l_file);
 					//printf("Vertices list x: %f\n",p_object->vertex[i].x);
                    fread (&vertices_unordered[i].y, sizeof(float), 1, l_file);
 					//printf("Vertices list y: %f\n",p_object->vertex[i].y);
					fread (&vertices_unordered[i].z, sizeof(float), 1, l_file);
 					//printf("Vertices list z: %f\n",p_object->vertex[i].z);
				}
				break;

			//--------------- TRI_FACEL1 ----------------
			// Description: Polygons (faces) list
			// Chunk ID: 4120 (hex)
			// Chunk Lenght: 1 x unsigned short (number of polygons) 
			//             + 3 x unsigned short (polygon points) x (number of polygons)
			//             + sub chunks
			//-------------------------------------------
			case 0x4120:
				fread (&l_qty, sizeof (unsigned short), 1, l_file);
                numOfPolygons = l_qty;
				indices = new Index[l_qty];
				if (!indices) return 0;
                //printf("Number of polygons: %d\n",l_qty); 
                for (i=0; i<l_qty; i++)
                {
					fread (&indices[i].a, sizeof (unsigned short), 1, l_file);
					//printf("Polygon point a: %d\n",p_object->polygon[i].a);
					fread (&indices[i].b, sizeof (unsigned short), 1, l_file);
					//printf("Polygon point b: %d\n",p_object->polygon[i].b);
					fread (&indices[i].c, sizeof (unsigned short), 1, l_file);
					//printf("Polygon point c: %d\n",p_object->polygon[i].c);
					fread (&l_face_flags, sizeof (unsigned short), 1, l_file);
					//printf("Face flags: %x\n",l_face_flags);
				}
                break;

			//------------- TRI_MAPPINGCOORS ------------
			// Description: Vertices list
			// Chunk ID: 4140 (hex)
			// Chunk Lenght: 1 x unsigned short (number of mapping points) 
			//             + 2 x float (mapping coordinates) x (number of mapping points)
			//             + sub chunks
			//-------------------------------------------
			case 0x4140:
				fread (&l_qty, sizeof (unsigned short), 1, l_file);
				texCoords_unordered = new Vector2[l_qty];
				if (!texCoords_unordered) return 0;
				for (i=0; i<l_qty; i++)
				{
					fread (&texCoords_unordered[i].x, sizeof (float), 1, l_file);
					//printf("Mapping list u: %f\n",p_object->mapcoord[i].u);
                    fread (&texCoords_unordered[i].y, sizeof (float), 1, l_file);
					//printf("Mapping list v: %f\n",p_object->mapcoord[i].v);
				}
                break;

			//----------- Skip unknow chunks ------------
			//We need to skip all the chunks that currently we don't use
			//We use the chunk lenght information to set the file pointer
			//to the same level next chunk
			//-------------------------------------------
			default:
				 fseek(l_file, l_chunk_lenght-6, SEEK_CUR);
        } 
	}
	fclose (l_file); // Closes the file stream

	// reorder vertices, texCoords, normals
	if (numOfPolygons == 0)
	{
		if (vertices_unordered)
			delete [] vertices_unordered;

		if (texCoords_unordered)
			delete [] texCoords_unordered;

		if (indices)
			delete [] indices;

		return 0;
	}

	// try to allocate memory for final mesh
	Mesh m;
	m.vertex_array = new Vertex[numOfPolygons*3];
	if (!m.vertex_array)
	{
		if (vertices_unordered)
			delete [] vertices_unordered;

		if (texCoords_unordered)
			delete [] texCoords_unordered;

		if (indices)
			delete [] indices;

		return 0;
	}

	// try to allocate memory for unordered normals
	Vector3* normals_unordered = new Vector3[numOfPolygons*3];
	if (!normals_unordered)
	{
		if (vertices_unordered)
			delete [] vertices_unordered;

		if (texCoords_unordered)
			delete [] texCoords_unordered;

		if (indices)
			delete [] indices;

		delete [] m.vertex_array;

		return 0;
	}

	// sort vertices, texture coords
	for (i=0; i<numOfPolygons; ++i)
    {
		Vertex a,b,c;
		a.vertex = vertices_unordered[indices[i].a];
		b.vertex = vertices_unordered[indices[i].b];
		c.vertex = vertices_unordered[indices[i].c];

		if (texCoords_unordered)
		{
			a.texCoord = texCoords_unordered[indices[i].a];
			b.texCoord = texCoords_unordered[indices[i].b];
			c.texCoord = texCoords_unordered[indices[i].c];
		}
		
		// average normals (so that they are smooth)
		Vector3 normal = (b.vertex - a.vertex).
			Cross(c.vertex - a.vertex);

		normals_unordered[indices[i].a] += normal;
		normals_unordered[indices[i].b] += normal;
		normals_unordered[indices[i].c] += normal;
		
		m.vertex_array[i*3] = a;
		m.vertex_array[i*3+1] = b;
		m.vertex_array[i*3+2] = c;
    }

	// sort normals
	for (int i=0; i<numOfPolygons; i++)
	{
		m.vertex_array[i*3].normal = normals_unordered[indices[i].a];
		m.vertex_array[i*3+1].normal = normals_unordered[indices[i].b];
		m.vertex_array[i*3+2].normal = normals_unordered[indices[i].c];
	}

	// normalize normals
	// calculate bounding box
	
	float minX = m.vertex_array[0].vertex.x;
	float minY = m.vertex_array[0].vertex.y;
	float minZ = m.vertex_array[0].vertex.z;
	float maxX = minX;
	float maxY = minY;
	float maxZ = minZ;
	for (int i=0; i<numOfPolygons*3; i++)
	{
		if (maxX < m.vertex_array[i].vertex.x)
		{
			maxX = m.vertex_array[i].vertex.x;
		}
		if (minX > m.vertex_array[i].vertex.x)
		{
			minX = m.vertex_array[i].vertex.x;
		}

		if (maxY < m.vertex_array[i].vertex.y)
		{
			maxY = m.vertex_array[i].vertex.y;
		}
		if (minY > m.vertex_array[i].vertex.y)
		{
			minY = m.vertex_array[i].vertex.y;
		}

		if (maxZ < m.vertex_array[i].vertex.z)
		{
			maxZ = m.vertex_array[i].vertex.z;
		}
		if (minZ > m.vertex_array[i].vertex.z)
		{
			minZ = m.vertex_array[i].vertex.z;
		}

		m.vertex_array[i].normal.Normalize();
	}

	BoundingBox box(maxX, minX, maxY, minY, maxZ, minZ);

	if (normals_unordered)
		delete [] normals_unordered;

	if (vertices_unordered)
		delete [] vertices_unordered;

	if (indices)
		delete [] indices;


	// assign vectors to arrays to be passed to 
	// the renderer
	Vector3 *vertices = new Vector3[numOfPolygons*3];
	Vector3 *normals = new Vector3[numOfPolygons*3];
	Vector2 *texCoords = 0;
	if (texCoords_unordered)
	{
		delete [] texCoords_unordered;
		texCoords = new Vector2[numOfPolygons*3];
	}
	for (int i=0; i<numOfPolygons*3; i++)
	{
		vertices[i] = m.vertex_array[i].vertex;
		normals[i] = m.vertex_array[i].normal;
		if (texCoords)
			texCoords[i] = m.vertex_array[i].texCoord;	
	}

	// create vbo's
	delete [] m.vertex_array;
	bool res = renderer->BindNodeToMesh
		(mesh, vertices, normals, texCoords, numOfPolygons*3);

	if (!res)
	{
		delete mesh;
		mesh = 0;
	}
	
	mesh->SetInitialBoundingBox(box);

	if (vertices)
		delete [] vertices;

	if (normals)
		delete [] normals;

	if (texCoords)
		delete [] texCoords;

	return mesh;
}